嗨大家好,我是Sean! 不知道大家連假,過得怎樣啊?
昨天我們講完Migration error的部分,我們來講解一下另外一種會造成migration error的情況,並解決它。
相信大家在初期設計資料庫時,或是要新增資料庫欄位時,一定都會遇到一個問題。
那就是,如果欄位設計錯時,該怎麼辦? 再者,如果想更改的欄位是foreign key可能會有fk constraint的問題。
那麼,我們今天來講解若原本的欄位是選項,要如何把它變更為foreign key呢?
首先,我們會看到我們的欄位大概會長得像這樣:
class People(models.Model):
...
AREA_CHOICES = [
('AREA1', 'East_Blue',),
('AREA2', 'West_Blue',),
('AREA3', 'North_Blue',),
('AREA4', 'South_Blue',),
]
area = models.CharField(max_length=100, choices=AREA_CHOICES)
...
我們想要達到的結果像是以下這樣:
class Area(models.Model):
name = models.CharField(max_length=100)
class People(models.Model):
...
area = models.ForeignKey(Area, on_delete=models.PROTECT, db_column='name')
...
好的讓我們開始執行這段轉換的過程。
首先第一步,我們在我們最原始的model樣子,也就是choice寫在class People裡的圖一,加入我們外來鍵。
加入外來鍵的前提是,我們同時新增他的model。
所以我們第一步增加的東西,如下所示:
# 我們新增的model
class Area(models.Model)
name = models.CharField(max_length=100)
class People(models.Model):
...
AREA_CHOICES = [
('AREA1', 'East_Blue',),
('AREA2', 'West_Blue',),
('AREA3', 'North_Blue',),
('AREA4', 'South_Blue',),
]
area = models.CharField(max_length=100, choices=AREA_CHOICES)
...
# 我們新增的foreign key欄位
area_fk = models.ForeignKey(Area, on_delete=models.PROTECT, db_column='area_fk', null=True)
...
新增的東西有Area的model,以及area_fk的外來鍵欄位。
特別值得一提的是,我們在加入外來鍵的area_fk欄位裡,給了兩個比較特別的參數,第一個是db_column,而第二個則是null=True。
他們的意義是,db_column可以使該欄位在db中的欄位強制更改為參數中的名字。
聽起來很像廢話,對吧? 明明在開頭的變數名稱就可以直接變為欄位名稱啊!
為什麼還需要db_column這個參數呢?
原因是因為當欄位被設定為foreign key時,他在db中的欄位會自動被加入 _id 的尾綴。
加入這樣的尾綴後,會導致後續我們在使用欄位的時候:
發生欄位名稱與資料庫中的欄位名稱不相符的錯誤!
接著,我們的第二個參數為null=True。其目的為讓我們的欄位可以為無,也就是null狀態。
允許在該欄位的model(Area),沒有任何資料的情況下,外來鍵可以為空
不會因為找不到資料關聯的情況,而發生了error!
接著我們用以上的model來做一次資料遷移,也就是使用:
python manage.py makemigrations
python manage.py migrate
接下來,我們再使用較為特殊的migration,來達成我們想變換foreign key的目的。
我們直接來使用以下指令,來看看他的效果:
python manage.py makemigrations --empty core
他新增了一個migration檔案,我們來看看他大概長得像怎樣。
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0002_auto_20221010_2211'),
]
operations = [
]
他會像是個完全空白的migration檔案,其中的operation並無任何的執行事項。
而就如同我們在之前的文章所提到的,我們可以經由主動改寫migration的檔案,來達到我們的目的。
如同我們下面的code所示:
from django.db import migrations
def fill_area_fk(apps, schema_editor):
People = apps.get_model('core', 'People')
Area = apps.get_model('core', 'Area')
for people in People.objects.all():
people.category_fk, created = area.objects.get_or_create(name=people.area)
people.save()
class Migration(migrations.Migration):
dependencies = [
('core', '0002_auto_20210715_0836'),
]
operations = [
migrations.RunPython(fill_area_fk),
]
我們可以在operation的地方,加入我們的功能來完成。
首先,我們先得到了People與Area兩個model。
接著,我們經由原本People中的area欄位裡的選項,來生成model Area中的資料!
再儲存每筆People的資料,就可以達到不需要額外匯入資料,我們每筆資料的fk,就與我們原本的選項相同了!
我們再經由:
python manage.py migrate
遷移我們剛剛寫好的migration檔案就可以,完成上述的目標了!
最後,我們只需要移除choice的欄位以及變更我們的fk欄位的名字,就大功告成了!
class People(models.Model):
...
# 我們移除choice,以及變更foreign key欄位名稱後的樣子
area = models.ForeignKey(Area, on_delete=models.PROTECT, db_column='area', null=True)
...
經過makemigrations後,請記得再確認一下
migration檔案的內容是否為移除欄位以及變更欄位名字!
他很有可能會變成以下這樣:
class Migration(migrations.Migration):
dependencies = [
('core', '0003_auto_20221010_2236'),
]
operations = [
migrations.RemoveField(
model_name='people',
name='area_fk',
),
migrations.AlterField(
model_name='people',
name='area',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='core.Area'),
),
]
AlterField為變更欄位屬性,這麼一來就會發生最一開始,直接變更欄位屬性的錯誤了!
正確的應該為下面這樣:
class Migration(migrations.Migration):
dependencies = [
('core', '0003_auto_20221010_2236'),
]
operations = [
migrations.RemoveField(
model_name='people',
name='area',
),
migrations.RenameField(
model_name='sample',
old_name='category_fk',
new_name='category',
),
]
Remove 選項欄位,以及Rename 外來鍵欄位!
想要達成這樣Migration檔案,你可以:
好的!這麼一來,我們由選項欄位更改為外來鍵的任務就大功告成了!
那麼今天的文章就到此結束! 感謝大家的收看!
我是Sean,你各位海上的人,我們明天見!